//============================================================================
// Name        : des.cpp
// Author      : Arun
// Version     : 0.1
// Copyright   : GNU GPL v3
// Description : DES engine
//============================================================================

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string>
#include <time.h>
#include "queue.h"
using namespace std;

#define SIM_TIME 10
#define EVENT_COUNT 10000

string strDist[4] = {"Exponential", "Poisson", "Geometric", "Constant(None)"};


FILE *fp, *f, *fp_ongoing_avg_wait, *fp_ongoing_avg_serv, *fp_ongoing_avg_syst, *fp_queue, *fp_dropped_packet, *fp_ongoing_avg_queu;
FILE *fp_special;

long MC;

long service_time;
long arrival_time;
long total_waiting_time;
float on_going_avg_waiting_time;
float on_going_avg_service_time;
float on_going_avg_queue_count;

long packet_count;
long dropped_packets;

struct queue_t input_queue;
struct queue_t processors;
struct item current_item_in_server;

enum dist
{
	EXPONENTIAL, POISSON, GEOMETRIC, CONSTANT
};

long getExponentialRV(float lambda)
{
	long expoRV = 0;
	float rv = (float)rand()/((float)RAND_MAX + 1);
	expoRV = log(1 - rv) / (-lambda);
	expoRV = expoRV == 0 ? 1:expoRV;
	return expoRV;
}

long getPoissonRV(float lambda){
	long poissonRV = 0;

	return poissonRV;
}

long getGeometricRV(float lambda){
	long geoRV = 0;

	return geoRV;
}



long generate_time_from_dist(int distribution, float lambda ){
	long ret_time;
	if(distribution == EXPONENTIAL ){
		ret_time = getExponentialRV(lambda);
	}
	else if(distribution == GEOMETRIC){
		ret_time = getGeometricRV(lambda);
	}
	else if(distribution == POISSON){
		ret_time = getPoissonRV(lambda);
	}
	else if(distribution == CONSTANT){
		ret_time = (long)1/lambda == 0 ? 1: (long)1/lambda;
	}
	return ret_time;
}

struct item build_packet(){
	struct item new_packet;
	new_packet.id = packet_count++;
	new_packet.arrival_time = arrival_time;
	return new_packet;
}

bool check_next_event(){
	if(input_queue.count == 0)
		return true;
	//else if(input_queue.queue[input_queue.remove_index].service_end_time < arrival_time)
	else if(current_item_in_server.service_end_time < arrival_time)
			return false;
	return true;
}

void writeInputToLog(
		float arrival_rate, int arrival_dist,
		float service_rate, int service_dist,
		int server_count, long sim_time){

	fprintf(fp,"Arrival-rate:%f Arrival-Dist:%s Service-rate:%f Service-Dist:%s No.OfServers:%d Sim-Time:%ld seconds\n", arrival_rate, strDist[arrival_dist].c_str(), service_rate, strDist[service_dist].c_str(), server_count, sim_time);

	fprintf(f,"Arrival-rate:%f Arrival-Dist:%s Service-rate:%f Service-Dist:%s No.OfServers:%d Sim-Time:%ld seconds\n", arrival_rate, strDist[arrival_dist].c_str(), service_rate, strDist[service_dist].c_str(), server_count, sim_time);
	fprintf(f, "MC\t\tArrTime\t\tServTime\tWaitTime\tSysTime\tQueueCount\tDroppPacket\tPacketCount\n");

	fprintf(fp_ongoing_avg_serv,"Arrival-rate:%f Arrival-Dist:%s Service-rate:%f Service-Dist:%s No.OfServers:%d Sim-Time:%ld seconds\n", arrival_rate, strDist[arrival_dist].c_str(), service_rate, strDist[service_dist].c_str(), server_count, sim_time);
	fprintf(fp_ongoing_avg_serv,"MC\tOnGoingServTime\n");

	fprintf(fp_ongoing_avg_wait,"Arrival-rate:%f Arrival-Dist:%s Service-rate:%f Service-Dist:%s No.OfServers:%d Sim-Time:%ld seconds\n", arrival_rate, strDist[arrival_dist].c_str(), service_rate, strDist[service_dist].c_str(), server_count, sim_time);
	fprintf(fp_ongoing_avg_wait,"MC\tOnGoingWaitTime\n");

	fprintf(fp_ongoing_avg_queu,"Arrival-rate:%f Arrival-Dist:%s Service-rate:%f Service-Dist:%s No.OfServers:%d Sim-Time:%ld seconds\n", arrival_rate, strDist[arrival_dist].c_str(), service_rate, strDist[service_dist].c_str(), server_count, sim_time);
	fprintf(fp_ongoing_avg_queu,"MC\tOnGoingQueue\n");

	fprintf(fp_ongoing_avg_syst,"Arrival-rate:%f Arrival-Dist:%s Service-rate:%f Service-Dist:%s No.OfServers:%d Sim-Time:%ld seconds\n", arrival_rate, strDist[arrival_dist].c_str(), service_rate, strDist[service_dist].c_str(), server_count, sim_time);
	fprintf(fp_ongoing_avg_syst,"MC\tOnGoingSysTime\n");

	fprintf(fp_queue,"Arrival-rate:%f Arrival-Dist:%s Service-rate:%f Service-Dist:%s No.OfServers:%d Sim-Time:%ld seconds\n", arrival_rate, strDist[arrival_dist].c_str(), service_rate, strDist[service_dist].c_str(), server_count, sim_time);
	fprintf(fp_queue,"MC\tQueueCount\n");

	fprintf(fp_dropped_packet,"Arrival-rate:%f Arrival-Dist:%s Service-rate:%f Service-Dist:%s No.OfServers:%d Sim-Time:%ld seconds\n", arrival_rate, strDist[arrival_dist].c_str(), service_rate, strDist[service_dist].c_str(), server_count, sim_time);
	fprintf(fp_dropped_packet,"MC\tDroppedPacketCount\n");
}


void init_sim(long queue_size, int processor_count){
	MC = 0;
	arrival_time = -1;
	service_time = -1;
	packet_count = 0;
	dropped_packets = 0;
	current_item_in_server.id = -1;
	total_waiting_time = 0;
	on_going_avg_waiting_time = 0;
	on_going_avg_service_time = 0;
	on_going_avg_queue_count = 0;

	if(queue_size == 0)
		create_queue(&input_queue);
	else
		create_queue(&input_queue, queue_size);

	create_queue(&processors, processor_count);

	// Log files
	fp = fopen("results/des_log.txt", "w");
	f = fopen("results/des_stats.txt","w");
	fp_ongoing_avg_serv = fopen("results/des_ongoing_serv_time.txt","w");
	fp_ongoing_avg_wait = fopen("results/des_ongoing_wait_time.txt","w");
	fp_ongoing_avg_queu = fopen("results/des_ongoing_queu_size.txt","w");
	fp_ongoing_avg_syst = fopen("results/des_ongoing_syst_size.txt","w");
	fp_queue = fopen("results/des_queue_status.txt","w");
	fp_dropped_packet = fopen("results/des_dropped_packet.txt","w");

	time_t current_time;
	time(&current_time);
	srand((unsigned int) current_time);

	fp_special = fopen("results/des_drop_packet_freq.txt","a");
}

void shutdown_sim()
{
	fclose(f);
	fclose(fp_ongoing_avg_wait);
	fclose(fp_ongoing_avg_serv);
	fclose(fp_ongoing_avg_queu);
	fclose(fp_ongoing_avg_syst);
	fclose(fp_queue);
	fclose(fp_dropped_packet);

	fclose(fp_special);
}

//// SINGLE SERVER

void handle_packet_arrival(int arrival_dist, float arrival_rate, int service_dist, float service_rate){
	MC = arrival_time;
	struct item new_packet = build_packet();
	int ret = enqueue(&input_queue, new_packet);
	if(ret == -1){
		dropped_packets++;
		arrival_time += generate_time_from_dist(arrival_dist, arrival_rate);
	}
	else
	{
		//sort_queue(&input_queue);
		if(input_queue.count == 1 && current_item_in_server.id == -1)
		{
			// Only one packet in system .No need to wait.
			service_time = generate_time_from_dist(service_dist, service_rate);
			on_going_avg_service_time = (on_going_avg_service_time * (packet_count- dropped_packets - 1) + service_time)  / (packet_count - dropped_packets);
			input_queue.queue[input_queue.remove_index].service_time = service_time;
			input_queue.queue[input_queue.remove_index].service_start_time = MC;
			input_queue.queue[input_queue.remove_index].service_end_time = MC + service_time;

			current_item_in_server = dequeue(&input_queue);

			// Statistics
			total_waiting_time += (arrival_time - MC);
			on_going_avg_waiting_time = (on_going_avg_waiting_time * (packet_count - dropped_packets - 1 ) + (arrival_time - MC))/(packet_count-dropped_packets);
		}
		arrival_time += generate_time_from_dist(arrival_dist, arrival_rate);
	}
}

void handle_packet_departure(int arrival_dist, float arrival_rate, int service_dist, float service_rate){
	current_item_in_server = dequeue(&input_queue);

	service_time = generate_time_from_dist(service_dist, service_rate);

	current_item_in_server.service_time = service_time;
	current_item_in_server.service_start_time = MC;
	current_item_in_server.service_end_time = MC + service_time;

	// Statistics
	total_waiting_time += (arrival_time - MC);
	on_going_avg_waiting_time = (on_going_avg_waiting_time * (packet_count - dropped_packets - 1 ) + (arrival_time - MC))/(packet_count - dropped_packets);
	on_going_avg_service_time = (on_going_avg_service_time * (packet_count- dropped_packets - 1) + service_time)  / (packet_count - dropped_packets);
	on_going_avg_queue_count = (on_going_avg_queue_count * (packet_count - dropped_packets -1) + input_queue.count) / (packet_count - dropped_packets);
	fprintf(fp, "\nMC: %ld\tOngoing avgwait: %f\tOngoing avgserv: %f\tOngoing avgsyst: %f\tqueueCount: %ld", MC, on_going_avg_waiting_time, on_going_avg_service_time, on_going_avg_waiting_time + on_going_avg_service_time, input_queue.count );
	fprintf(f, "%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\n", MC, arrival_time, service_time, arrival_time-MC, arrival_time-MC+service_time, input_queue.count, dropped_packets, packet_count);
	fprintf(fp_ongoing_avg_wait,"%ld\t%f\n", MC, on_going_avg_waiting_time);
	fprintf(fp_ongoing_avg_serv,"%ld\t%f\n", MC, on_going_avg_service_time);
	fprintf(fp_ongoing_avg_syst,"%ld\t%f\n", MC, on_going_avg_waiting_time + on_going_avg_service_time);
	fprintf(fp_ongoing_avg_queu,"%ld\t%f\n", MC, on_going_avg_queue_count);
	fprintf(fp_dropped_packet, "%ld\t%ld\n",MC, dropped_packets);
	fprintf(fp_queue,"%ld\t%ld\n", MC, input_queue.count);

}


/// MULTIPLE SERVERS without QUEUES
bool check_next_event_nprocessors(){
	if(processors.count == 0)
		return true;
	//else if(input_queue.queue[input_queue.remove_index].service_end_time < arrival_time)
	else if(processors.queue[processors.remove_index].service_end_time < arrival_time)
			return false;
	return true;
}


void handle_packet_arrival_nprocessors(int arrival_dist, float arrival_rate, int service_dist, float service_rate){
	MC = arrival_time;
	struct item new_packet = build_packet();
	if(processors.count < processors.size){
		// Only one packet in system .No need to wait.
		service_time = generate_time_from_dist(service_dist, service_rate);
		new_packet.service_time = service_time;
		new_packet.service_start_time = MC;
		new_packet.service_end_time = MC + service_time;

		// Statistics
		on_going_avg_service_time = (on_going_avg_service_time * (packet_count- dropped_packets - 1) + service_time)  / (packet_count - dropped_packets);

		int ret = enqueue(&processors, new_packet);
		if(ret == -1){
			dropped_packets++;
			arrival_time += generate_time_from_dist(arrival_dist, arrival_rate);
		}
		sort_queue(&processors);
	}
	else
	{
		dropped_packets++;
	}
	arrival_time += generate_time_from_dist(arrival_dist, arrival_rate);
}

void handle_packet_departure_nprocessors(int arrival_dist, float arrival_rate, int service_dist, float service_rate){
	struct item done_with_processing = dequeue(&processors);

	// Statistics
	on_going_avg_service_time = (on_going_avg_service_time * (packet_count- dropped_packets - 1) + service_time)  / (packet_count - dropped_packets);
	fprintf(fp_ongoing_avg_serv,"%ld\t%f\n", MC, on_going_avg_service_time);
	fprintf(fp_dropped_packet, "%ld\t%ld\n",MC, dropped_packets);
	fprintf(fp_queue,"%ld\t%ld\n", MC, processors.count);

}


//////

int startDES(
		float arrival_rate, int arrival_dist,
		float service_rate, int service_dist,
		int server_count, long queue_size,
		int end_condition, long sim_time=SIM_TIME, long event_count = EVENT_COUNT){

	// Init stuff
	init_sim(queue_size, server_count);

	// Generate first packet
	arrival_time = generate_time_from_dist(arrival_dist, (float)arrival_rate);

	if(end_condition == 1)
	{
		// write input parameters to log files
		writeInputToLog(arrival_rate, arrival_dist, service_rate, service_dist, server_count, sim_time);

		// Do stop conditpion based on sim time
		while(MC < sim_time*1000){
			//// Add logic here..
			// Check event
			bool is_arrival_first = server_count == 1 ? check_next_event():check_next_event_nprocessors();
			if(is_arrival_first){
				server_count == 1 ? handle_packet_arrival(arrival_dist, arrival_rate, service_dist, service_rate) :
						handle_packet_arrival_nprocessors(arrival_dist, arrival_rate, service_dist, service_rate);
			}
			else{
				server_count == 1 ? handle_packet_departure(arrival_dist, arrival_rate, service_dist, service_rate) :
						handle_packet_departure_nprocessors(arrival_dist, arrival_rate, service_dist, service_rate);
			}
		}
		fprintf(fp,"Packets: %ld Dropped Packets:%ld BlockingRate:%f",packet_count, dropped_packets, ((float)dropped_packets/(float)packet_count)*100.0);
		fprintf(fp_special, "%f\t%ld\t%ld\t%f\n",(1/arrival_rate)/1000, packet_count, dropped_packets, ((float)dropped_packets/(float)packet_count)*100.0);
	}
	else if(end_condition == 2)
	{ 	// Do stop condition based on event count

	}
	else
	{	// Default case - do for event_count = DEFAULT (10000)

	}
	shutdown_sim();
	return 0;
}



int main(int argc, char** argv)  {
	if(argc < 8 ){ //argc < 8){
		cout << "Usage: ./des arrival-rate arrival-dist service-rate service-dist server-count queue-size end-condition [sim-time [event-count]]\n";
		cout << "arrival-rate: Rate of arrival. Eg: arrival mean is 10 msec\n";
		cout << "arrival-dist: Type of distribution 0- Exponential 1- Poisson 2-Geometric 3-constant at 1/lambda time\n";
		cout << "service-rate: Rate of service/processing. Eg: service mean is 10 msec\n";
		cout << "service-dist: Type of distribution 0- Exponential 1- Poisson 2-Geometric 3-constant at 1/lambda time\n";
		cout << "server-count: Number of server elements (VC's)\n";
		cout << "queue-size:   Size of the queue. By default INFINITY (100000)\n";
		cout << "end-condition: Simulation end condition. 1- Simulation time bound (specify sim-time), 2- Event bound (specify event-count)\n";
		cout << "sim-time: Time in simulation seconds the simulation will run";
		cout << "event-count: Specify this to end simulation when event count reaches event-count\n";
		return -1;
	}
	//startDES((float)1/(1000*10),0,(float)1/(3*60*1000),0,50,0,1,1000*60);
	if(argc == 9)
		startDES(1/atof(argv[1]),atoi(argv[2]),1/atof(argv[3]),atoi(argv[4]),atoi(argv[5]), atol(argv[6]),atoi(argv[7]), atol(argv[8]));
	if(argc == 10)
		startDES(1/atof(argv[1]),atoi(argv[2]),1/atof(argv[3]),atoi(argv[4]),atoi(argv[5]), atol(argv[6]),atoi(argv[7]), atol(argv[8]),atol(argv[9]));

	fprintf(fp, "\nMC: %ld Total avgwait: %f Total avgserv: %f Total avgsyst: %f", MC, on_going_avg_waiting_time, on_going_avg_service_time, on_going_avg_waiting_time + on_going_avg_service_time );
	fclose(fp);
	return 0;
}
